package org.web.abc.stockanalysis.server.controller;

import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.http.*;
import org.springframework.web.multipart.MultipartFile;
import org.web.base.domain.ViewResultHelper;
import org.web.base.domain.exception.ResultMessageEnum;
import org.web.base.domain.exception.ServiceException;
import org.web.base.domain.helper.ServiceExceptionHelper;
import org.web.base.domain.view.ViewResultDTO;
import org.web.base.domain.ResultDO;
import org.web.base.helper.*;
import org.web.base.helper.excel.*;
import org.web.base.helper.excel.ExcelOperator;
import org.web.base.helper.excel.ExcelHelper;
import org.web.base.helper.excel.OneRowDTO;
import org.web.base.helper.excel.OneTask;

import java.io.InputStream;
import java.lang.reflect.*;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.*;

/***
* 只用一个类下载所有实体的Excel文件。 完全继承查询接口提供的功能。
* 未来的扩展功能：
* 1. 增加权限控制，数据权限，围绕entityName的权限控制.
* 2. 控制下载Excel的数量，避免下载过大的文件。
* 3. 返回记录总数。
* */
@Controller()
@RequestMapping("stockAnalysisServerExcel")

    public class ExcelController {
    private static final Logger logger = LogManager.getLogger(ExcelController.class);

    private static final String QUERY_BASE_PACKAGE = "org.web.abc.stockanalysis.server.domain.query.";
    private static final String DTO_BASE_PACKAGE = "org.web.abc.stockanalysis.sdk.dto.";
    private static final String DOMAIN_BASE_PACKAGE = "org.web.abc.stockanalysis.server.domain";
    private final BeanFactory beanFactory;

    public ExcelController(BeanFactory beanFactory) {
        this.beanFactory = beanFactory;
    }

    private static final Gson gson = new Gson();

    /**
    * 未来的扩展功能：
    * 1. 增加权限控制，数据权限，围绕entityName的权限控制.
    * 2. 控制下载Excel的数量，避免下载过大的文件。
    * 3. 返回记录总数。
    */
    @RequestMapping("/downloadExcel")
    @ResponseBody
    public Object downloadExcel(@RequestBody Map<String, Object> params) {
        try {
            ExcelOperator excelOperator = buildExcelOperator(params);
            List<Map<String, String>> attributeLabelMapList = null;
            if (params.get("attributeLabelMapList") != null) {
                String attributeLabelMapListStr = params.get("attributeLabelMapList").toString();
                Gson gson = new Gson();
                Type listType = new TypeToken<List<Map<String, String>>>() {}.getType();
                attributeLabelMapList = gson.fromJson(attributeLabelMapListStr, listType);
            }
            checkExcelOperator(beanFactory, excelOperator);
            Class<?> queryClass = Class.forName(excelOperator.getQueryClassName());
            Class<?> dtoClass = Class.forName(excelOperator.getDtoClassName());
            Object queryBean = BeanConvertHelper.convertMapToBean(params, queryClass);

            ResultDO resultDO = getResultDOByQuery(excelOperator, queryBean, beanFactory);
            if (resultDO.isFailed()) {
                throw ServiceExceptionHelper.buildServiceException(resultDO);
            }
            List<OneTask> queryTaskList = new ArrayList<>();
            OneTask oneTask = new OneTask();
            String entityName = dtoClass.getSimpleName().replace("DTO", "");
            oneTask.setTaskMark("Query-" + entityName + "-1");
            oneTask.setQueryObject(queryBean);
            oneTask.setResultDO(resultDO);
            queryTaskList.add(oneTask);
            String fileName = buildDownloadExcelFileName(excelOperator);
            byte[] excelContent = DownloadHelper.exportData2Byte(queryTaskList, attributeLabelMapList, excelOperator, false);
            HttpHeaders headers = new HttpHeaders();
            headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
            String encodedFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8.toString()).replace("+", "%20");
            headers.set("Content-Disposition", "attachment; filename*=" + encodedFileName);
            return new ResponseEntity<>(excelContent, headers, HttpStatus.OK);
        } catch (Exception e) {
            logger.error(ServiceExceptionHelper.getExceptionInfo(e));
            return gson.toJson(ViewResultHelper.buildViewResultByServiceException(e, null));
        }
    }

    private ExcelOperator buildExcelOperator(Map<String, Object> params) {
        ExcelOperator excelOperator = new ExcelOperator();
        excelOperator.setEntityName((String) params.get("entityName"));
        excelOperator.setServiceBeanName((String) params.get("serviceBeanName"));
        excelOperator.setQueryClassName((String) params.get("queryClassName"));
        excelOperator.setDomainClassName((String) params.get("domainClassName"));
        excelOperator.setDtoClassName((String) params.get("dtoClassName"));
        excelOperator.setSelectMethodName((String) params.get("selectMethodName"));
        initExcelOperator(excelOperator);
        return excelOperator;
    }

    /***
     * 根据上传的文件下载Excel文件，上传的文件中包含查询条件。
     * 下载文件的sheet名称为 , 由查询条件的rowMark组成。
     * */
    @PostMapping("/downloadExcelByFile")
    @ResponseBody
    public Object downloadExcelByFile(MultipartFile file, ExcelOperator excelOperator) {

        try {
            initExcelOperator(excelOperator);
            checkExcelOperator(beanFactory, excelOperator);

            Class<?> queryClass = Class.forName(excelOperator.getQueryClassName());

            if (file.isEmpty()) {
                throw ServiceExceptionHelper.buildServiceException(ResultMessageEnum.PARAM_EMPTY, "上传文件为空。");
            }
            InputStream inputStream = file.getInputStream();
            // 使用 Apache POI 读取 Excel 文件
            Workbook workbook = new XSSFWorkbook(inputStream);
            inputStream.close(); // 读完立即关闭
            Sheet sheet = workbook.getSheet("DownloadQuery");
            // 遍历 Excel 表格
            Class<?> queryClazz = Class.forName(excelOperator.getQueryClassName());
            List<OneRowDTO> queryList = DownloadHelper.sheetToListMap(sheet, queryClazz);

            Object serviceBean = beanFactory.getBean(excelOperator.getServiceBeanName());
            Method method = serviceBean.getClass().getMethod(excelOperator.getSelectMethodName(), queryClass);
            method.setAccessible(true);

            List<OneTask> queryTaskList = new ArrayList<>();
            Class dtoClass = Class.forName(excelOperator.getDtoClassName());
            int i = 1;
            for (OneRowDTO oneRowDTO : queryList) {
                Object queryBean = BeanConvertHelper.convertMapToBean(oneRowDTO.getRowDataMap(), queryClass);
                ResultDO resultDO = (ResultDO<List<?>>) method.invoke(serviceBean, queryBean);
                OneTask oneTask = new OneTask();
                String entityName = dtoClass.getSimpleName().replace("DTO", "");
                if (StringUtils.isNotBlank(oneRowDTO.getRowMark())) {
                    oneTask.setTaskMark(oneRowDTO.getRowMark());
                } else {
                    oneTask.setTaskMark("Query-" + entityName + "-" + i);
                }
                oneTask.setQueryObject(queryBean);
                oneTask.setResultDO(resultDO);
                queryTaskList.add(oneTask);
                i++;
            }

            byte[] excelContent = DownloadHelper.exportData2Byte(queryTaskList,null, excelOperator, false);

            String fileName = buildDownloadExcelFileName(excelOperator);
            HttpHeaders headers = new HttpHeaders();
            headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
            headers.setContentDispositionFormData("attachment", fileName);
            return new ResponseEntity<>(excelContent, headers, HttpStatus.OK);
        } catch (Exception e) {
            logger.error(ServiceExceptionHelper.getExceptionInfo(e));
            return ViewResultHelper.buildViewResultByServiceException(e, null);
        }
    }
	
	private static ResultDO<List<?>> getResultDOByQuery(ExcelOperator excelOperator, Object queryBean, BeanFactory beanFactory) throws Exception {
		Class<?> queryClass = Class.forName(excelOperator.getQueryClassName());
		Object serviceBean = beanFactory.getBean(excelOperator.getServiceBeanName());
		Method method = serviceBean.getClass().getMethod(excelOperator.getSelectMethodName(), queryClass);
		method.setAccessible(true);
		return (ResultDO<List<?>>) method.invoke(serviceBean, queryBean);
	}

	/***
	* 默认从service层获取数据，当然也可以指定从BIZ层获取数据。
	* 二开时，需要考虑存在案例漏洞的风险，从两个方面入手：限死 【selectMethodName】名称，限定【entityName】范围。
	* 初始化的规则，可以调整
	* @param excelOperator 下载标识对象
	* */
	private static void initExcelOperator(ExcelOperator excelOperator) {

		if (excelOperator == null) {
			throw ServiceExceptionHelper.buildServiceException(ResultMessageEnum.PARAM_EMPTY, "下载标识对象为空。");
		} else if (excelOperator.getEntityName() == null || excelOperator.getEntityName().isEmpty()) {
			throw ServiceExceptionHelper.buildServiceException(ResultMessageEnum.PARAM_EMPTY, "entityName信息不能为空。");
		}
		String entityName = excelOperator.getEntityName();
		entityName = StringHelper.initUpper(entityName);
		String initLowerEntityName = StringHelper.initLower(entityName);
		if (excelOperator.getServiceBeanName() == null || excelOperator.getServiceBeanName().isEmpty()) {
			excelOperator.setServiceBeanName(initLowerEntityName + "Service");
		}

		if (excelOperator.getQueryClassName() == null || excelOperator.getQueryClassName().isEmpty()) {
			excelOperator.setQueryClassName(QUERY_BASE_PACKAGE + "Query" + entityName);
		}

		if (excelOperator.getDomainClassName() == null || excelOperator.getDomainClassName().isEmpty()) {
			excelOperator.setDomainClassName(DOMAIN_BASE_PACKAGE + entityName + "DO");
		}

		if (excelOperator.getDtoClassName() == null || excelOperator.getDtoClassName().isEmpty()) {
			excelOperator.setDtoClassName(DTO_BASE_PACKAGE + entityName + "DTO");
		}

		if (excelOperator.getSelectMethodName() == null || excelOperator.getSelectMethodName().isEmpty()) {
			excelOperator.setSelectMethodName("select" + entityName + "ListResult");
		}
	}

	/**
	* 校验资源是否存在，主要是检查serviceBeanName、dtoClassName、queryClassName是否存在。
	*
	* @param beanFactory   spring bean工厂
	* @param excelOperator 下载标识对象
	* @throws ServiceException 资源不存在异常
	*/
	private static void checkExcelOperator(BeanFactory beanFactory, ExcelOperator excelOperator) {
		if (!beanFactory.containsBean(excelOperator.getServiceBeanName())) {
			throw ServiceExceptionHelper.buildServiceException(ResultMessageEnum.DATA_NOT_EXIST, "查询资源不存。访问资源entityName信息： " + excelOperator.getEntityName());
		} else if (!beanFactory.containsBean(excelOperator.getServiceBeanName())) {
			throw ServiceExceptionHelper.buildServiceException(ResultMessageEnum.DATA_NOT_EXIST, "查询参数类不存在。访问资源serviceBeanName信息： " + excelOperator.getServiceBeanName());
		}
		try {
			Class.forName(excelOperator.getDtoClassName());
			Class.forName(excelOperator.getQueryClassName());
		} catch (ClassNotFoundException e) {
			throw ServiceExceptionHelper.buildServiceException(ResultMessageEnum.DATA_NOT_EXIST, "DTO或查询参数类不存在。访问资源dtoClassName或queryClassName信息： " + excelOperator.getDtoClassName() + " 或 " + excelOperator.getQueryClassName());
		}
	}


	private static String buildDownloadExcelFileName(ExcelOperator excelOperator) {
		String currentTime = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss").format(new Date());
		return "Data-" + excelOperator.getEntityName() + "_" + currentTime + ".xlsx";
   }
    }

